# -*- coding: utf-8 -*-
"""
:Author: Fernando Pacheco <fernando.pacheco@ingesur.com.uy>
:Date: Sun, 14 Jun 2009 00:33:01 -0300
:copyright: Ingesur SRL
:license: GPL Version 2
"""
import threading
import time

from hydrogeo import HydroGeo
from basefunctions import BaseFunctions

class WellFunctions(HydroGeo,BaseFunctions):
    """
    Base class for hydrogeo library. Common function in hydrogeology.

    La biblioteca contiene la funcion de pozo para acuiferos libres, semiconfinados y confinados.
    
    :note: Some functions in the class have very particular names (without under case)
    """
    def __init__(self, u = None, a = None, ap = None, gen = True):
        """
        Class initialization function
        
        :Parameters:
            u : list of float
                u =  r*r*S/(4*T*t)
            a : list of float
                a =  r/B = observation_distance/lakely_coef
            ap : float
                ap =  rp/B = well_radius/lakely_coef
            gen: bool
                Generate Wua for large data in separate thread.
        """
        #HidroGeo.__init__(self)
        #BaseFunctions.__init__(self)
        if u:
            if type(u) == list:
                self.u = u
            else:
                temp = []
                temp.append(u)
                self.u = temp
        else:
            self.u = self._u()
        # a = r/B
        if a:
            if type(a) == list:
                self.a = a
            else:
                temp = []
                temp.append(a)
                self.a = temp
        else:
            # Coherent values for r/B
            self.a = self._a()
        # ap = rp/B or None
        self.ap = ap
        # Variable to store data of Wua
        if gen:
            self.gen_Wua()
        else:
            print "Wua not generated."

    def _a(self):
        """
        Generate Standart r/B values

        :Parameters:
            :return: Coherent values for a
            :rtype: list of float
        """
        a=[0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007, 0.008, 0.009, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09,0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5]
        return (a)

    def _u(self):
        """
        Standart u values

        :Parameters:
            :return: Coherent values for u
            :rtype: list of float
        """
        temp = [0.000000001, 0.000000002, 0.000000003, 0.000000004, 0.000000005, 0.000000006, 0.000000007, 0.000000008, 0.000000009]
        # u values for W(u,a)
        u = []
        for ex in range(0,11):
            for i in range(0,9):
                u.append( temp[i]*10**ex )
        return (u)

    def Wu(self, tipo='series'):
        """
        Well function for confined and uncondined aquifers

        :Parameters:
            tipo : string
                'serie' = Use series expansion to calculate else use integrate
        """
        if tipo=='series':
            return self.wu_series(self.u)
        else:
            return self.wu_integrate(self.u)

    def _wu_integrate(self, u):
        """
        Integral of function fu between u and ls

        :Parameters:
            u : list float
                u is r^2*S/(4*T*t)
        """
        uu = []
        for t in u:
            uu.append( self.wu_integrate(t) )
        return uu


    def _wu_series(self, u):
        """
        Series expansion of Wu function

        :Parameters:
            u : list float
                u is r^2*S/(4*T*t)
        """
        uu = []
        for t in u:
            uu.append( self.wu_series(t) )
        return uu

    def gen_Wua(self):
        """
        Generate Wua
        """
        self.wua_data = None
        self.wua_data_u = None
        # Separated thread to generate Wua
        self.thr = threading.Thread(target=self._generate_wua)
        self.thr.start()
        #self.con = threading.Condition()
        # Espera a que termine
        self.thr.join()
            
    def Wua(self, uu, aa, tipo ='mem'):
        """
        Interpolate Wua from values in dictionary
        
        :Parameters:
            uu : float
                Integral variable
            aa : float
                Integral variable
            tipo : string
                Integral variable
        """
        if tipo == 'mem':
            return ( self.wua_from_dictionary(uu, aa) )
        else:
            return ( self.wua_integrate(uu, aa) )

    def _find(self,data,aa,uu,reverse=True):
        """
        Documentation
        """
        if reverse:
            mult=-1
        else:
            mult=1
        cont = aa[1]
        sigo=True
        while sigo:
            if round(data[cont]['a'],10)==round(aa[0],10) and round(data[cont]['u'],10)==round(uu[0],10):
                sigo=False
            else:
                cont = cont + mult*1
        #print "Find encontro=%s" % data[cont]['wua']
        return data[cont]['wua']

    def _busca_limites(self,aa,uu):
        """
        Auxiliar function to find wua data by a and u to interpolate

        :Parameters:
            aa : list of float
                Value to find wua
            uu : list of float
                Lower left value of a
        """
        # print aa,uu
        # One a and one u requiered
        data=self.wua_data
        if not aa[0] and not uu[0]:
            print "***HydrogGeo Error: Not parameters in call***"
            return None
        # Set control variables
        if aa[2]:
            searching_a_ma=True
        else:
            searching_a_ma=False
        if uu[2]:
            searching_u_ma=True
        else:
            searching_u_ma=False
        wua_00=None
        wua_01=None
        wua_10=None
        wua_11=None
        if searching_a_ma and searching_u_ma:
            # Se busca todo
            wua_00=self._find(data,[aa[0],aa[1]],[uu[0],uu[1]],reverse=True)
            wua_01=self._find(data,[aa[0],aa[1]],[uu[2],uu[3]],reverse=True)
            wua_10=self._find(data,[aa[2],aa[3]],[uu[0],uu[1]],reverse=False)
            wua_11=self._find(data,[aa[2],aa[3]],[uu[2],uu[3]],reverse=False)
        elif searching_a_ma:
            wua_00=self._find(data,[aa[0],aa[1]],[uu[0],uu[1]],reverse=True)
            wua_10=self._find(data,[aa[2],aa[3]],[uu[0],uu[1]],reverse=False)
        elif searching_u_ma:
            wua_00=self._find(data,[aa[0],aa[1]],[uu[0],uu[1]],reverse=True)
            wua_01=self._find(data,[aa[0],aa[1]],[uu[2],uu[3]],reverse=True)
        else:
            wua_00=self._find(data,[aa[0],aa[1]],[uu[0],uu[1]],reverse=True)

        return [
                {'a':aa[0], 'u':uu[0], 'wua':wua_00},
                {'a':aa[0], 'u':uu[2], 'wua':wua_01},
                {'a':aa[2], 'u':uu[0], 'wua':wua_10},
                {'a':aa[2], 'u':uu[2], 'wua':wua_11}
                ]

    def wua_from_dictionary(self, u, a):
        """
        Return Wua(u,a) from interpolation of values in dictionary

        :Parameters:
            u : float
                Value of u to obtain data
            a : float
                Value of a to obtain data
        """
        data = self.wua_data
        data_u = self.wua_data_u
        n = len(data)
        # Not in range of a or u
        if not (data[0]['a'] <= a <= data[n-1]['a']) or not (data_u[0]['u'] <= u <= data_u[n-1]['u']):
            print "***HydroGeo: u and a must be in range***"
            return None
        a_find = False
        a_mi=None
        a_ma=None
        a_mi_pos=-1
        a_ma_pos=-1
        u_find = False
        u_mi=None
        u_ma=None
        u_mi_pos=-1
        u_ma_pos=-1  
        # TODO: Efficient way
        for i in range(0,n):
            # Ordered list by a
            if not a_find:
                if round(data[i]['a'],10) == round(a,10):
                    a_find=True
                    a_mi = data[i]['a']
                    # Get first position of
                    a_mi_pos=i
                elif data[i]['a'] < a:
                    a_mi = data[i]['a']
                    # Get last position of
                    a_mi_pos=i
                elif data[i]['a'] > a:
                    a_find=True
                    a_ma = data[i]['a']
                    # Get first position of
                    a_ma_pos=i

            # Ordered list by u
            if not u_find:
                if round(data_u[i]['u'],10) == round(u,10):                    
                    u_mi = data_u[i]['u']
                    u_mi_pos=i
                    u_find=True
                elif data_u[i]['u'] < u:
                    u_mi = data_u[i]['u']
                    u_mi_pos=i
                elif data_u[i]['u'] > u:
                    u_ma = data_u[i]['u']
                    u_ma_pos=i
                    u_find=True
            # Both find exit for
            if a_find and u_find:
                break        
        # Interpolar
        #print "*****Previo: (a=%s,u=%s)=(a_mi=%s,a_mi_pos=%s,a_ma=%s,a_ma_pos=%s,u_mi=%s,u_mi_pos=%s,u_ma=%s,u_ma_pos=%s)" % (a,u,a_mi,a_mi_pos,a_ma,a_ma_pos,u_mi,u_mi_pos,u_ma,u_ma_pos)
        aa=[a_mi,a_mi_pos,a_ma,a_ma_pos]
        uu=[u_mi,u_mi_pos,u_ma,u_ma_pos,]
        dres=self._busca_limites(aa,uu)
        #print "Resultado=%s" % val_mi
        # TODO: One or more interpolations
        # i1=self.linear_interpolation(x,pa,pb)
        if a_mi and a_ma:
            if u_mi and u_ma:
                # (a_mi,u_mi,wua)-(a_ma,u_mi,wua)
                wua_a_u1=self.linear_interpolation(a,[dres[0]['a'],dres[0]['wua']],[dres[2]['a'],dres[2]['wua']])
                # (a_mi,u_ma,wua)-(a_ma,u_ma,wua)
                wua_a_u2=self.linear_interpolation(a,[dres[1]['a'],dres[1]['wua']],[dres[3]['a'],dres[3]['wua']])
                # Finally
                return ( self.linear_interpolation(u,[dres[0]['u'],wua_a_u1],[dres[1]['u'],wua_a_u2]) )
            else:
                # Only u_mi
                return ( self.linear_interpolation(a,[dres[0]['a'],dres[0]['wua']],[dres[2]['a'],dres[2]['wua']]) )
        else:
            #Only a_mi
            if u_mi and u_ma:
                # (a_mi,u_mi,wua)-(a_ma,u_mi,wua)
                return ( self.linear_interpolation(u,[dres[0]['u'],dres[0]['wua']],[dres[1]['u'],dres[1]['wua']]) )
            else:
                return dres[0]['wua']

    def _generate_wua(self):
        """
        Generate Wua data in python list of dictionaries to get fixed values on memory and interpolate (later) quickly
        """
        # List to store data
        wuadata = []
        for ta in self.a:            
            for u in self.u:
                uval = {}
                uval['a']= ta
                uval['u']= u
                i = self.wua_integrate(u, ta)
                uval['wua']= i[0]
                wuadata.append(uval)
        # Sorted list
        from operator import itemgetter
        self.wua_data = sorted(wuadata,key=itemgetter('a'))
        self.wua_data_u = sorted(wuadata,key=itemgetter('u'))
        print "Wua_integrate: Listo"        